package tjava.ast;

import japa.parser.ASTHelper;
import japa.parser.JavaParser;
import japa.parser.ParseException;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.ImportDeclaration;
import japa.parser.ast.PackageDeclaration;
import japa.parser.ast.TypeParameter;
import japa.parser.ast.body.BodyDeclaration;
import japa.parser.ast.body.ClassOrInterfaceDeclaration;
import japa.parser.ast.body.ConstructorDeclaration;
import japa.parser.ast.body.FieldDeclaration;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.ModifierSet;
import japa.parser.ast.body.Parameter;
import japa.parser.ast.body.TypeDeclaration;
import japa.parser.ast.body.VariableDeclarator;
import japa.parser.ast.expr.ArrayAccessExpr;
import japa.parser.ast.expr.ArrayCreationExpr;
import japa.parser.ast.expr.ArrayInitializerExpr;
import japa.parser.ast.expr.AssignExpr;
import japa.parser.ast.expr.BinaryExpr;
import japa.parser.ast.expr.BooleanLiteralExpr;
import japa.parser.ast.expr.CastExpr;
import japa.parser.ast.expr.ConditionalExpr;
import japa.parser.ast.expr.EnclosedExpr;
import japa.parser.ast.expr.Expression;
import japa.parser.ast.expr.FieldAccessExpr;
import japa.parser.ast.expr.InstanceOfExpr;
import japa.parser.ast.expr.IntegerLiteralExpr;
import japa.parser.ast.expr.MethodCallExpr;
import japa.parser.ast.expr.NameExpr;
import japa.parser.ast.expr.NullLiteralExpr;
import japa.parser.ast.expr.ObjectCreationExpr;
import japa.parser.ast.expr.StringLiteralExpr;
import japa.parser.ast.expr.ThisExpr;
import japa.parser.ast.expr.UnaryExpr;
import japa.parser.ast.expr.VariableDeclarationExpr;
import japa.parser.ast.stmt.BlockStmt;
import japa.parser.ast.stmt.DoStmt;
import japa.parser.ast.stmt.EmptyStmt;
import japa.parser.ast.stmt.ExpressionStmt;
import japa.parser.ast.stmt.ForStmt;
import japa.parser.ast.stmt.ForeachStmt;
import japa.parser.ast.stmt.IfStmt;
import japa.parser.ast.stmt.ReturnStmt;
import japa.parser.ast.stmt.Statement;
import japa.parser.ast.stmt.ThrowStmt;
import japa.parser.ast.stmt.TryStmt;
import japa.parser.ast.stmt.WhileStmt;
import japa.parser.ast.type.ClassOrInterfaceType;
import japa.parser.ast.type.PrimitiveType;
import japa.parser.ast.type.ReferenceType;
import japa.parser.ast.type.Type;
import japa.parser.ast.type.VoidType;
import japa.parser.ast.type.WildcardType;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.TypeParameterDeclaration;
import com.sun.mirror.type.ClassType;
import com.sun.mirror.type.TypeMirror;

/**
 * Generates template instance of the specified generic class.
 * 
 * @author Anna Yudina
 * 
 */
public class ClassGenerator {

	final String pathToJavaSrc;
	final static Primitives primitives = new Primitives();

	final AnnotationProcessorEnvironment env;

	final List<TypeParameterDeclaration> cFormalTypeParameters;

	final String cPackageQualifiedName;
	final String cSimpleName;
	final String cQualifiedName;

	final String gcSimpleName;

	final String scPackageQualifiedName;
	final String scSimpleName;
	final String scQualifiedName;

	final List<TypeMirror> scActualTypeArguments;
	final List<TypeParameterDeclaration> scFormalTypeParameters;

	final List<ClassOrInterfaceDeclaration> nestedTypes;

	/**
	 * @param cDeclaration
	 * @param env
	 */
	public ClassGenerator(final ClassDeclaration cDeclaration,
			AnnotationProcessorEnvironment env) {

		final String pathToJavaSrcEnv = System.getenv("JAVA_SRC");
		try {
		 if (pathToJavaSrcEnv == null) {
			throw new Exception("JAVA_SRC not found");
		} 
		} catch (Exception e) {
			e.printStackTrace();
		}
		this.pathToJavaSrc = pathToJavaSrcEnv + "/share/classes/";
		
		this.env = env;

		this.cPackageQualifiedName = cDeclaration.getPackage()
				.getQualifiedName();
		this.cQualifiedName = cDeclaration.getQualifiedName();
		this.cSimpleName = cDeclaration.getSimpleName();

		this.gcSimpleName = cSimpleName + "Gen";

		ClassType scType = cDeclaration.getSuperclass();
		ClassDeclaration scDeclaration = scType.getDeclaration();

		this.scSimpleName = scDeclaration.getSimpleName();
		this.scQualifiedName = scDeclaration.getQualifiedName();
		this.scPackageQualifiedName = scDeclaration.getPackage()
				.getQualifiedName();

		this.cFormalTypeParameters = (List<TypeParameterDeclaration>) cDeclaration
				.getFormalTypeParameters();

		this.scFormalTypeParameters = (List<TypeParameterDeclaration>) scDeclaration
				.getFormalTypeParameters();
		this.scActualTypeArguments = (List<TypeMirror>) scType
				.getActualTypeArguments();

		assert scFormalTypeParameters.size() == scActualTypeArguments.size();

		nestedTypes = new ArrayList<ClassOrInterfaceDeclaration>();
	}

	/**
	 * Generates a compilation unit for the template instance.
	 * 
	 * @throws ParseException
	 * @throws IOException
	 * @return
	 */
	public CompilationUnit generate() throws ParseException, IOException {

		FileInputStream fileInputStream = new FileInputStream(
				findSourceFile(scSimpleName));

		CompilationUnit original;
		try {
			original = JavaParser.parse(fileInputStream);
		} finally {
			fileInputStream.close();
		}

		CompilationUnit generated = createCU(original);

		return generated;
	}

	/**
	 * Source file lookup.
	 * 
	 * @param simpleName
	 * @return
	 */
	private String findSourceFile(String simpleName) {
		String filePath = null;

		if (new File(simpleName + ".java").exists()) {
			filePath = simpleName + ".java";

		} else if (new File(cPackageQualifiedName.replace(".", "/") + "/"
				+ simpleName + ".java").exists()) {
			filePath = cPackageQualifiedName.replace(".", "/") + "/"
					+ simpleName + ".java";

		} else if (new File("src/" + cPackageQualifiedName.replace(".", "/")
				+ "/" + simpleName + ".java").exists()) {
			filePath = "src/" + cPackageQualifiedName.replace(".", "/") + "/"
					+ simpleName + ".java";

		} else if (new File(pathToJavaSrc
				+ scPackageQualifiedName.replace(".", "/") + "/" + simpleName
				+ ".java").exists()) {
			filePath = pathToJavaSrc + scPackageQualifiedName.replace(".", "/")
					+ "/" + simpleName + ".java";

		}
		return filePath;
	}

	/**
	 * Fills the compilation unit.
	 * 
	 * @param original
	 * @return
	 */
	private CompilationUnit createCU(final CompilationUnit original) {

		CompilationUnit generated = new CompilationUnit();

		if (!cPackageQualifiedName.isEmpty()) {
			generated.setPackage(new PackageDeclaration(ASTHelper
					.createNameExpr(cPackageQualifiedName)));
		}

		List<ImportDeclaration> generatedImports = new ArrayList<ImportDeclaration>();
		List<ImportDeclaration> originalImports = original.getImports();
		if (!scPackageQualifiedName.isEmpty()) {
			ImportDeclaration import1 = new ImportDeclaration();
			import1.setName(new NameExpr(scPackageQualifiedName + ".*"));
			generatedImports.add(import1);
		}
		if (originalImports != null) {
			for (ImportDeclaration import2 : originalImports) {
				generatedImports.add(import2);
			}
		}
		generated.setImports(generatedImports);

		ClassOrInterfaceDeclaration generatedType = new ClassOrInterfaceDeclaration(
				ModifierSet.PUBLIC, false, gcSimpleName);

		List<TypeDeclaration> types = original.getTypes();
		for (TypeDeclaration type : types) {
			if (type instanceof ClassOrInterfaceDeclaration) {
				if (type.getName().equals(scSimpleName)) {
					ASTHelper.addTypeDeclaration(generated, generatedType);

					generatedType.setTypeParameters(getTypeParameters());
					generatedType.setAnnotations(type.getAnnotations());
					generatedType.setModifiers(type.getModifiers());
					generatedType
							.setImplements(substituteTypeParametersList(((ClassOrInterfaceDeclaration) type)
									.getImplements()));
					generatedType
							.setExtends(substituteTypeParametersList(((ClassOrInterfaceDeclaration) type)
									.getExtends()));
					try {
						generateSuperclasses(((ClassOrInterfaceDeclaration) type)
								.getExtends());
						generateSuperclasses(((ClassOrInterfaceDeclaration) type)
								.getImplements());
					} catch (IOException e) {
						e.printStackTrace();
					}
					findInnerClasses(type.getMembers());
					generatedType.setMembers(modifyMembers(gcSimpleName,
							type.getMembers()));
				}
			}
		}
		return generated;
	}

	/**
	 * @param members
	 */
	private void findInnerClasses(final List<BodyDeclaration> members) {
		List<BodyDeclaration> genMembers = members;
		if (genMembers != null) {
			for (BodyDeclaration member : genMembers) {
				if (member instanceof ClassOrInterfaceDeclaration) {
					ClassOrInterfaceDeclaration nestedType = new ClassOrInterfaceDeclaration();
					nestedType.setName(((ClassOrInterfaceDeclaration) member)
							.getName());
					List<TypeParameter> typeParameters = null;
					if (((ClassOrInterfaceDeclaration) member)
							.getTypeParameters() != null) {
						typeParameters = new ArrayList<TypeParameter>();
						for (TypeParameter typeParameter : ((ClassOrInterfaceDeclaration) member)
								.getTypeParameters()) {
							typeParameters.add(new TypeParameter(typeParameter
									.getName(), null));
						}
					}
					nestedType.setTypeParameters(typeParameters);
					nestedTypes.add(nestedType);
				}
			}
		}
	}

	/**
	 * Generates local versions of superclasses.
	 * 
	 * @param extendsList
	 * @throws IOException
	 */
	private void generateSuperclasses(List<ClassOrInterfaceType> extendsList)
			throws IOException {
		if (extendsList != null) {
			for (ClassOrInterfaceType cit : extendsList) {
				String fileName = cPackageQualifiedName + "." + cit.getName();
				File outputFile = new File("aptgen/src/"
						+ fileName.replace(".", "/") + ".java");
				if (!outputFile.exists()) {
					String pathToSource = findSourceFile(cit.getName());
					if (pathToSource != null) {
						FileInputStream fileInputStream;
						fileInputStream = new FileInputStream(pathToSource);
						try {
							CompilationUnit original;
							original = JavaParser.parse(fileInputStream);

							CompilationUnit generated = original;
							if (!cPackageQualifiedName.isEmpty()) {
								generated
										.setPackage(new PackageDeclaration(
												ASTHelper
														.createNameExpr(cPackageQualifiedName)));
							}
							List<ImportDeclaration> importDeclarations = new ArrayList<ImportDeclaration>();
							if (!scPackageQualifiedName.isEmpty()) {
								ImportDeclaration importDeclaration = new ImportDeclaration();
								importDeclaration.setName(new NameExpr(
										scPackageQualifiedName + ".*"));
								importDeclarations.add(importDeclaration);
							}
							if (generated.getImports() != null) {
								importDeclarations.addAll(generated
										.getImports());
							}
							generated.setImports(importDeclarations);

							PrintWriter writer = env.getFiler()
									.createSourceFile(fileName);
							writer.println(generated.toString());

							List<TypeDeclaration> types = original.getTypes();
							for (TypeDeclaration type : types) {
								if (type instanceof ClassOrInterfaceDeclaration) {
									generateSuperclasses(((ClassOrInterfaceDeclaration) type)
											.getExtends());
									generateSuperclasses(((ClassOrInterfaceDeclaration) type)
											.getImplements());
								}
							}
						} catch (FileNotFoundException e) {
							e.printStackTrace();
						} catch (ParseException e) {
							e.printStackTrace();
						} finally {
							fileInputStream.close();
						}
					}
				}
			}
		}
	}

	/**
	 * @param types
	 * @return
	 */
	private List<TypeParameter> getInnerClassTypeParameters(
			final List<TypeParameter> types) {
		if (types != null) {
			List<TypeParameter> genTypes = new ArrayList<TypeParameter>();
			for (TypeParameter t : types) {
				genTypes.add(t);
			}
			if (types != null) {
				int j = 0;
				for (TypeParameter t : types) {
					boolean found = false;
					int i = 0;
					for (TypeParameterDeclaration typeParameter : scFormalTypeParameters) {
						if (t.getName().equals(typeParameter.getSimpleName())) {
							found = true;
							for (TypeParameterDeclaration cTypeParameter : cFormalTypeParameters) {
								if (scActualTypeArguments.get(i).toString()
										.equals(cTypeParameter.getSimpleName())) {
									found = false;
									genTypes.get(j).setName(
											cTypeParameter.getSimpleName());
								}
							}
						}
						if (found) {
							genTypes.remove(t);
						}
						i++;
					}
				}
				j++;
				if (genTypes.isEmpty()) {
					genTypes = null;
				}
			}
			return genTypes;
		} else {
			return null;
		}
	}

	/**
	 * @param types
	 * @return
	 */
	private List<Type> getInnerClassTypes(final List<Type> types) {
		if (types != null) {
			List<Type> genTypes = new ArrayList<Type>();
			for (Type t : types) {
				genTypes.add(t);
			}
			int j = 0;
			for (Type t : types) {
				if (t instanceof ReferenceType) {
					ClassOrInterfaceType cit = (ClassOrInterfaceType) ((ReferenceType) t)
							.getType();
					boolean found = false;
					boolean found2 = false;
					int i = 0;
					for (TypeParameterDeclaration typeParameter : scFormalTypeParameters) {
						if (cit.getName().equals(typeParameter.getSimpleName())) {
							found = true;

							for (TypeParameterDeclaration cTypeParameter : cFormalTypeParameters) {
								if (scActualTypeArguments.get(i).toString()
										.equals(cTypeParameter.getSimpleName())) {
									found2 = true;
									ReferenceType rt = new ReferenceType();
									ClassOrInterfaceType cit2 = new ClassOrInterfaceType(
											scActualTypeArguments.get(i)
													.toString());
									rt.setType(cit2);
									genTypes.remove(j);
									genTypes.add(j, cit2);
								}
							}
						}
						if (found && !found2) {
							genTypes.remove(t);
						}
						i++;
					}
					if (!found) {
						substituteTypeParameters(cit);
						genTypes.remove(j);
						genTypes.add(j, cit);
					}

				} else if (t instanceof ClassOrInterfaceType) {
					boolean found = false;
					int i = 0;
					for (TypeParameterDeclaration typeParameter : scFormalTypeParameters) {
						if (((ClassOrInterfaceType) t).getName().equals(
								typeParameter.getSimpleName())) {
							found = true;
							for (TypeParameterDeclaration cTypeParameter : cFormalTypeParameters) {
								if (scActualTypeArguments.get(i).toString()
										.equals(cTypeParameter.getSimpleName())) {
									found = false;
									ReferenceType rt = new ReferenceType();
									ClassOrInterfaceType cit2 = new ClassOrInterfaceType(
											scActualTypeArguments.get(i)
													.toString());
									rt.setType(cit2);
									genTypes.remove(j);
									genTypes.add(j, cit2);
								}
							}
						}
						if (found) {
							genTypes.remove(t);
						}
						i++;
					}

				} else if (t instanceof WildcardType) {
					// TODO

				} else {
					// TODO
				}
			}
			j++;
			if (genTypes.isEmpty()) {
				genTypes = null;
			}
			return genTypes;
		} else {
			return null;
		}
	}

	/**
	 * @return
	 */
	private List<TypeParameter> getTypeParameters() {
		List<TypeParameter> typeParameters = new ArrayList<TypeParameter>();
		for (TypeParameterDeclaration parameterDeclaration : cFormalTypeParameters) {
			TypeParameter parameter = new TypeParameter();
			parameter.setName(parameterDeclaration.getSimpleName());
			typeParameters.add(parameter);
		}
		if (typeParameters.isEmpty()) {
			typeParameters = null;
		}
		return typeParameters;
	}

	/**
	 * @param className
	 * @param constructor
	 * @return
	 */
	private ConstructorDeclaration modifyConstructor(final String className,
			final ConstructorDeclaration constructor) {
		ConstructorDeclaration genConstructor = constructor;
		genConstructor.setName(className);
		genConstructor.setJavaDoc(null);
		if (genConstructor.getParameters() != null) {
			List<Parameter> parameters = genConstructor.getParameters();
			for (Parameter p : parameters) {
				modifyType(p.getType(), false);
			}
		}
		return genConstructor;
	}

	/**
	 * @param exp
	 */
	private void modifyExpression(final Expression exp) {
		try {
			if (exp instanceof VariableDeclarationExpr) {
				Type type = ((VariableDeclarationExpr) exp).getType();
				modifyType(type, false);
				List<VariableDeclarator> varDeclarators = ((VariableDeclarationExpr) exp)
						.getVars();
				if (varDeclarators != null) {
					for (VariableDeclarator varDeclarator : varDeclarators) {
						Expression init = varDeclarator.getInit();
						modifyExpression(init);
					}
				}

			} else if (exp instanceof CastExpr) {
				Type type = ((CastExpr) exp).getType();
				modifyType(type, false);

			} else if (exp instanceof UnaryExpr) {
				modifyExpression(((UnaryExpr) exp).getExpr());

			} else if (exp instanceof FieldAccessExpr) {
				List<Type> types = ((FieldAccessExpr) exp).getTypeArgs();
				if (types != null) {
					for (Type type : types) {
						modifyType(type, false);
					}
				}
				modifyExpression(((FieldAccessExpr) exp).getScope());

			} else if (exp instanceof BinaryExpr) {
				modifyExpression(((BinaryExpr) exp).getLeft());
				modifyExpression(((BinaryExpr) exp).getRight());

			} else if (exp instanceof AssignExpr) {
				modifyExpression(((AssignExpr) exp).getTarget());
				modifyExpression(((AssignExpr) exp).getValue());

			} else if (exp instanceof MethodCallExpr) {
				List<Type> types = ((MethodCallExpr) exp).getTypeArgs();
				if (types != null) {
					for (Type type : types) {
						modifyType(type, false);
					}
				}
				List<Expression> exs = ((MethodCallExpr) exp).getArgs();
				if (exs != null) {
					for (Expression ex : exs) {
						modifyExpression(ex);
					}
				}
				modifyExpression(((MethodCallExpr) exp).getScope());

			} else if (exp instanceof EnclosedExpr) {
				modifyExpression(((EnclosedExpr) exp).getInner());

			} else if (exp instanceof IntegerLiteralExpr) {

			} else if (exp instanceof ArrayAccessExpr) {
				modifyExpression(((ArrayAccessExpr) exp).getIndex());
				modifyExpression(((ArrayAccessExpr) exp).getName());

			} else if (exp instanceof ObjectCreationExpr) {
				List<Expression> exs = ((ObjectCreationExpr) exp).getArgs();
				if (exs != null) {
					for (Expression ex : exs) {
						modifyExpression(ex);
					}
				}
				modifyExpression(((ObjectCreationExpr) exp).getScope());
				substituteTypeParameters(((ObjectCreationExpr) exp).getType());
				List<Type> types = ((ObjectCreationExpr) exp).getTypeArgs();
				if (types != null) {
					for (Type type : types) {
						modifyType(type, false);
					}
				}

			} else if (exp instanceof ThisExpr) {
				modifyExpression(((ThisExpr) exp).getClassExpr());

			} else if (exp instanceof ConditionalExpr) {
				modifyExpression(((ConditionalExpr) exp).getCondition());
				modifyExpression(((ConditionalExpr) exp).getThenExpr());
				modifyExpression(((ConditionalExpr) exp).getElseExpr());

			} else if (exp instanceof ArrayCreationExpr) {
				List<Expression> exs = ((ArrayCreationExpr) exp)
						.getDimensions();
				if (exs != null) {
					for (Expression ex : exs) {
						modifyExpression(ex);
					}
				}
				modifyType(((ArrayCreationExpr) exp).getType(), false);
				modifyExpression(((ArrayCreationExpr) exp).getInitializer());

			} else if (exp instanceof ArrayInitializerExpr) {
				List<Expression> exs = ((ArrayInitializerExpr) exp).getValues();
				if (exs != null) {
					for (Expression ex : exs) {
						modifyExpression(ex);
					}
				}

			} else if (exp instanceof InstanceOfExpr) {
				modifyExpression(((InstanceOfExpr) exp).getExpr());
				modifyType(((InstanceOfExpr) exp).getType(), false);

			} else if (exp instanceof NameExpr) {
				if (((NameExpr) exp).getName().equals(scSimpleName)) {
					((NameExpr) exp).setName(gcSimpleName);
				}

			} else if (exp instanceof NullLiteralExpr) {

			} else if (exp instanceof BooleanLiteralExpr) {

			} else if (exp instanceof StringLiteralExpr) {

			} else if (exp != null) {
				throw new Exception("Case " + exp.getClass().toString()
						+ " cannot be processed.");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * @param field
	 * @return
	 */
	private FieldDeclaration modifyField(final FieldDeclaration field) {
		FieldDeclaration genField = field;
		genField.setJavaDoc(null);
		modifyType(genField.getType(), false);
		return genField;
	}

	/**
	 * @param innerClass
	 * @return
	 */
	private ClassOrInterfaceDeclaration modifyInnerClass(
			final ClassOrInterfaceDeclaration innerClass) {
		ClassOrInterfaceDeclaration genInnerClass = innerClass;
		genInnerClass.setJavaDoc(null);
		genInnerClass
				.setTypeParameters(getInnerClassTypeParameters(genInnerClass
						.getTypeParameters()));
		substituteTypeParametersList(genInnerClass.getExtends());
		substituteTypeParametersList(genInnerClass.getImplements());
		modifyMembers(genInnerClass.getName(), genInnerClass.getMembers());
		return genInnerClass;
	}

	/**
	 * @param className
	 * @param members
	 * @return
	 */
	private List<BodyDeclaration> modifyMembers(final String className,
			final List<BodyDeclaration> members) {
		List<BodyDeclaration> genMembers = members;
		if (genMembers != null) {
			try {
				for (BodyDeclaration member : genMembers) {
					if (member instanceof FieldDeclaration) {
						FieldDeclaration field = (FieldDeclaration) member;
						modifyField(field);
					} else if (member instanceof ConstructorDeclaration) {
						ConstructorDeclaration constructor = (ConstructorDeclaration) member;
						modifyConstructor(className, constructor);
					} else if (member instanceof MethodDeclaration) {
						MethodDeclaration method = (MethodDeclaration) member;
						modifyMethod(method);
					} else if (member instanceof ClassOrInterfaceDeclaration) {
						ClassOrInterfaceDeclaration innerClass = (ClassOrInterfaceDeclaration) member;
						modifyInnerClass(innerClass);
					} else if (member != null) {
						throw new Exception("Case "
								+ member.getClass().toString()
								+ " cannot be processed.");
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return genMembers;
	}

	/**
	 * @param method
	 * @return
	 */
	private MethodDeclaration modifyMethod(final MethodDeclaration method) {
		MethodDeclaration genMethod = method;
		genMethod.setJavaDoc(null);
		if (method.getBody() != null) {
			List<Statement> statements = method.getBody().getStmts();
			if (statements != null) {
				for (Statement s : statements) {
					modifyStatement(s);
				}
			}
		}
		modifyType(genMethod.getType(), false);
		if (genMethod.getParameters() != null) {
			List<Parameter> parameters = genMethod.getParameters();
			for (Parameter p : parameters) {
				modifyType(p.getType(), false);
			}
		}
		return genMethod;
	}

	/**
	 * @param s
	 */
	private void modifyStatement(final Statement s) {
		try {
			if (s instanceof ExpressionStmt) {
				modifyExpression(((ExpressionStmt) s).getExpression());

			} else if (s instanceof TryStmt) {
				modifyStatement(((TryStmt) s).getTryBlock());

			} else if (s instanceof IfStmt) {
				modifyExpression(((IfStmt) s).getCondition());
				modifyStatement(((IfStmt) s).getThenStmt());
				modifyStatement(((IfStmt) s).getElseStmt());

			} else if (s instanceof ReturnStmt) {
				modifyExpression(((ReturnStmt) s).getExpr());

			} else if (s instanceof WhileStmt) {
				modifyExpression(((WhileStmt) s).getCondition());
				modifyStatement(((WhileStmt) s).getBody());

			} else if (s instanceof ForStmt) {
				modifyStatement(((ForStmt) s).getBody());
				modifyExpression(((ForStmt) s).getCompare());
				if (((ForStmt) s).getInit() != null) {
					for (Expression e : ((ForStmt) s).getInit()) {
						modifyExpression(e);
					}
				}
				if (((ForStmt) s).getUpdate() != null) {
					for (Expression e : ((ForStmt) s).getUpdate()) {
						modifyExpression(e);
					}
				}

			} else if (s instanceof ThrowStmt) {
				modifyExpression(((ThrowStmt) s).getExpr());

			} else if (s instanceof BlockStmt) {
				for (Statement stmt : ((BlockStmt) s).getStmts()) {
					modifyStatement(stmt);
				}

			} else if (s instanceof DoStmt) {
				modifyStatement(((DoStmt) s).getBody());
				modifyExpression(((DoStmt) s).getCondition());

			} else if (s instanceof ForeachStmt) {
				modifyStatement(((ForeachStmt) s).getBody());
				modifyExpression(((ForeachStmt) s).getIterable());
				modifyExpression(((ForeachStmt) s).getVariable());

			} else if (s instanceof EmptyStmt) {

			} else if (s != null) {
				throw new Exception("Case " + s.getClass().toString()
						+ " cannot be processed.");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * @param t
	 */
	private void modifyType(final Type t, final boolean usePrimitives) {
		try {
			if (t instanceof ReferenceType) {
				ClassOrInterfaceType oldType = (ClassOrInterfaceType) ((ReferenceType) t)
						.getType();
				modifyTypeNames(oldType, usePrimitives);
				modifyType(oldType.getScope(), usePrimitives);
				((ReferenceType) t).setType(oldType);

			} else if (t instanceof ClassOrInterfaceType) {
				modifyTypeNames((ClassOrInterfaceType) t, usePrimitives);

			} else if (t instanceof PrimitiveType) {

			} else if (t instanceof VoidType) {

			} else if (t != null) {
				throw new Exception("Case " + t.getClass().toString()
						+ " cannot be processed.");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * @param originalType
	 * @param usePrimitives
	 */
	private void modifyTypeNames(final ClassOrInterfaceType originalType,
			final boolean usePrimitives) {

		ClassOrInterfaceType genType = originalType;

		if (genType.getName().equals(scSimpleName)) {
			genType.setName(gcSimpleName);
			List<Type> typeArgs = new ArrayList<Type>();
			for (TypeParameterDeclaration typeParameter : cFormalTypeParameters) {
				ClassOrInterfaceType typeArg = new ClassOrInterfaceType(
						typeParameter.getSimpleName());
				typeArgs.add(typeArg);
			}
			if (typeArgs.isEmpty()) {
				typeArgs = null;
			}
			genType.setTypeArgs(typeArgs);
		}

		else {
			boolean isNested = false;
			for (ClassOrInterfaceDeclaration nestedType : nestedTypes) {
				if (genType.getName().equals(nestedType.getName())
						&& genType.getScope() == null) {
					isNested = true;
					genType.setTypeArgs(getInnerClassTypes(genType
							.getTypeArgs()));
				}
			}
			if (!isNested) {
				int i = 0;
				String newTypeName = null;
				boolean match = false;
				for (TypeParameterDeclaration typeParameter : scFormalTypeParameters) {
					if (!match) {
						if (genType.getName().equals(
								typeParameter.getSimpleName())) {
							if (usePrimitives
									&& primitives.getPrimitives().containsKey(
											scActualTypeArguments.get(i)
													.toString())) {
								newTypeName = primitives.getPrimitives()
										.get(scActualTypeArguments.get(i)
												.toString());
							} else {
								newTypeName = scActualTypeArguments.get(i)
										.toString();
							}

							match = true;
						}
						substituteTypeParameters(genType);
						i++;
					}
				}
				if (!match) {
					newTypeName = genType.getName();
				}
				genType.setName(newTypeName);
			}
		}
	}

	/**
	 * @param originalType
	 * @return
	 */
	private ClassOrInterfaceType substituteTypeParameters(
			final ClassOrInterfaceType originalType) {

		ClassOrInterfaceType genType = originalType;

		List<Type> genTypeArgs = null;
		if (genType.getTypeArgs() != null) {
			genTypeArgs = new ArrayList<Type>();

			boolean isNested = false;
			for (ClassOrInterfaceDeclaration nestedType : nestedTypes) {
				if (!isNested) {
					if (genType.getName().equals(nestedType.getName())
							&& genType.getScope() == null) {
						isNested = true;
						genType.setTypeArgs(getInnerClassTypes(genType
								.getTypeArgs()));
						ClassOrInterfaceType newType;
						newType = new ClassOrInterfaceType(genType.getName());
						newType.setTypeArgs(genType.getTypeArgs());
						newType.setScope(genType.getScope());
						if (genType.getTypeArgs() != null) {
							genTypeArgs.addAll(genType.getTypeArgs());
						}
					}
				}
			}

			if (!isNested) {
				for (Type typeArg : genType.getTypeArgs()) {
					Type oldTypeArg = null;
					try {
						if (typeArg instanceof ReferenceType) {
							oldTypeArg = (ClassOrInterfaceType) ((ReferenceType) typeArg)
									.getType();

						} else if (typeArg instanceof ClassOrInterfaceType) {
							oldTypeArg = ((ClassOrInterfaceType) typeArg);

						} else if (typeArg instanceof WildcardType) {
							oldTypeArg = ((WildcardType) typeArg);

						} else {
							throw new Exception("Case "
									+ typeArg.getClass().toString()
									+ " cannot be processed.");
						}
					} catch (Exception e) {
						e.printStackTrace();
					}

					if (oldTypeArg instanceof ClassOrInterfaceType) {
						ClassOrInterfaceType newTypeArg;
						int i = 0;
						String newTypeArgName = null;
						boolean match = false;
						for (TypeParameterDeclaration typeParameter : scFormalTypeParameters) {
							if (!match) {
								if (((ClassOrInterfaceType) oldTypeArg)
										.getName().equals(
												typeParameter.getSimpleName())) {
									newTypeArgName = scActualTypeArguments.get(
											i).toString();
									match = true;
								}
								substituteTypeParameters((ClassOrInterfaceType) oldTypeArg);
								i++;
							}
						}
						if (!match) {
							newTypeArgName = ((ClassOrInterfaceType) oldTypeArg)
									.getName();
						}
						newTypeArg = new ClassOrInterfaceType(newTypeArgName);
						newTypeArg
								.setTypeArgs(((ClassOrInterfaceType) oldTypeArg)
										.getTypeArgs());
						newTypeArg.setScope(((ClassOrInterfaceType) oldTypeArg)
								.getScope());
						genTypeArgs.add(newTypeArg);

					} else if (oldTypeArg instanceof WildcardType) {
						ReferenceType extType = new ReferenceType();
						ClassOrInterfaceType oldExtType = (ClassOrInterfaceType) ((WildcardType) oldTypeArg)
								.getExtends().getType();
						modifyTypeNames(oldExtType, false);
						extType.setType(oldExtType);

						ReferenceType supType = ((WildcardType) oldTypeArg)
								.getSuper();

						WildcardType newTypeArg = new WildcardType(extType,
								supType);
						genTypeArgs.add(newTypeArg);

					}
					if (genTypeArgs.isEmpty()) {
						genTypeArgs = null;
					}

					genType.setTypeArgs(genTypeArgs);
				}
			}
		}
		return genType;
	}

	/**
	 * @param originalTypes
	 * @return
	 */
	private List<ClassOrInterfaceType> substituteTypeParametersList(
			final List<ClassOrInterfaceType> originalTypes) {

		List<ClassOrInterfaceType> genTypes = null;
		if (originalTypes != null) {
			genTypes = new ArrayList<ClassOrInterfaceType>();
			for (ClassOrInterfaceType originalType : originalTypes) {
				ClassOrInterfaceType genType = substituteTypeParameters(originalType);
				genTypes.add(genType);
			}
		}

		return genTypes;
	}

}